#include "sdk.h"
#include "find_replace.h"
#include <wx/progdlg.h>
#include "M_Code_StyledTextCtrl.h"
#include "confirmreplacedlg.h"
#include "encodingdetector.h"
#include "findreplacedlg.h"
#include "searchresultslog.h"

struct cbFindReplaceData {
  int start;
  int end;
  wxString findText;
  wxString replaceText;
  bool initialreplacing;
  bool findInFiles;
  bool delOldSearches;
  bool sortSearchResult;
  bool matchWord;
  bool startWord;
  bool startFile;
  bool matchCase;
  bool regEx;
  bool directionDown;
  bool originEntireScope;
  int scope;
  wxString searchPath;
  wxString searchMask;
  int searchProject;
  int searchTarget;
  bool recursiveSearch;
  bool hiddenSearch;
  bool NewSearch;
  int SearchInSelectionStart;
  int SearchInSelectionEnd;
  bool autoWrapSearch;
  bool findUsesSelectedText;
  bool multiLine;
  bool fixEOLs;
  int eolMode;
  void ConvertEOLs( int newmode );
  bool IsMultiLine();
  cbFindReplaceData() {
    eolMode = wxSCI_EOL_LF;
    fixEOLs = false;
  }
};

void cbFindReplaceData::ConvertEOLs( int newmode ) {
  if( eolMode != newmode ) {
    const wxChar* eol_lf = _T( "\n" );
    const wxChar* eol_crlf = _T( "\r\n" );
    const wxChar* eol_cr = _T( "\r" );
    const wxChar* eol_from = eol_lf;
    const wxChar* eol_to = eol_lf;
    switch( eolMode ) {
      case wxSCI_EOL_CR:
        eol_from = eol_cr;
        break;
      case wxSCI_EOL_CRLF:
        eol_from = eol_crlf;
        break;
      default:
        ;
    }
    switch( newmode ) {
      case wxSCI_EOL_CR:
        eol_to = eol_cr;
        break;
      case wxSCI_EOL_CRLF:
        eol_to = eol_crlf;
        break;
      default:
        newmode = wxSCI_EOL_LF;
    }
    findText.Replace( eol_from, eol_to, true );
    replaceText.Replace( eol_from, eol_to, true );
    eolMode = newmode;
  }
}

bool cbFindReplaceData::IsMultiLine() {
  if( regEx ) {
    return multiLine;
  }
  return ( ( findText.Find( _T( "\n" ) ) != wxNOT_FOUND ) || ( findText.Find( _T( "\r" ) ) != wxNOT_FOUND ) );
}

FindReplace::FindReplace() :
  m_LastFindReplaceData( nullptr ) {
}

FindReplace::~FindReplace() {
  delete m_LastFindReplaceData;
}

int FindReplace::ShowFindDialog( bool replace, bool explicitly_find_in_files ) {
  wxString phraseAtCursor;
  bool hasSelection = false;
  M_Code_StyledTextCtrl* control = nullptr;
  EditorManager *editorMgr = Manager::Get()->GetEditorManager();
  M_Code_Editor* ed = editorMgr->GetBuiltinEditor( editorMgr->GetActiveEditor() );
  if( ed ) {
    control = ed->GetControl();
    hasSelection = control->GetSelectionStart() != control->GetSelectionEnd();
    int wordStart = control->WordStartPosition( control->GetCurrentPos(), true );
    int wordEnd = control->WordEndPosition( control->GetCurrentPos(), true );
    wxString wordAtCursor = control->GetTextRange( wordStart, wordEnd );
    phraseAtCursor = control->GetSelectedText();
    if( control->LineFromPosition( control->GetSelectionStart() )
        == control->LineFromPosition( control->GetSelectionEnd() ) ) {
      hasSelection = false;
    }
    if( phraseAtCursor.IsEmpty() ) {
      phraseAtCursor = wordAtCursor;
    }
    int selstartline = control->LineFromPosition( control->GetSelectionStart() );
    int selendline   = control->LineFromPosition( control->GetSelectionEnd() );
    if( selstartline != selendline ) {
      phraseAtCursor = wxEmptyString;
    }
  }
  FindReplaceBase* dlg = new FindReplaceDlg( Manager::Get()->GetAppWindow(), phraseAtCursor, hasSelection, !replace, !ed, explicitly_find_in_files );
  PlaceWindow( dlg );
  if( dlg->ShowModal() == wxID_CANCEL ) {
    dlg->Destroy();
    return -2;
  }
  if( dlg->GetFindString().empty() ) {
    dlg->Destroy();
    msging( _T( "Can't look for an empty search criterion!" ), _T( "错误" ), wxOK | wxICON_EXCLAMATION, Manager::Get()->GetAppWindow() );
    return -2;
  }
  if( !m_LastFindReplaceData ) {
    m_LastFindReplaceData = new cbFindReplaceData;
  }
  m_LastFindReplaceData->start = 0;
  m_LastFindReplaceData->end = 0;
  m_LastFindReplaceData->findText = dlg->GetFindString();
  m_LastFindReplaceData->replaceText = dlg->GetReplaceString();
  m_LastFindReplaceData->eolMode = wxSCI_EOL_LF;
  m_LastFindReplaceData->multiLine = dlg->GetMultiLine();
  m_LastFindReplaceData->fixEOLs = dlg->GetFixEOLs();
  m_LastFindReplaceData->startFile = dlg->GetStartFile();
  m_LastFindReplaceData->findInFiles = dlg->IsFindInFiles();
  if( !m_LastFindReplaceData->findInFiles ) {
    m_LastFindReplaceData->autoWrapSearch = dlg->GetAutoWrapSearch();
    if( !replace ) {
      m_LastFindReplaceData->findUsesSelectedText = dlg->GetFindUsesSelectedText();
    }
  }
  m_LastFindReplaceData->delOldSearches = dlg->GetDeleteOldSearches();
  m_LastFindReplaceData->sortSearchResult = dlg->GetSortSearchResult();
  m_LastFindReplaceData->matchWord = dlg->GetMatchWord();
  m_LastFindReplaceData->startWord = dlg->GetStartWord();
  m_LastFindReplaceData->matchCase = dlg->GetMatchCase();
  m_LastFindReplaceData->regEx = dlg->GetRegEx();
  m_LastFindReplaceData->directionDown = dlg->GetDirection() == 1;
  m_LastFindReplaceData->originEntireScope = dlg->GetOrigin() == 1;
  m_LastFindReplaceData->scope = dlg->GetScope();
  m_LastFindReplaceData->searchPath = dlg->GetSearchPath();
  m_LastFindReplaceData->searchMask = dlg->GetSearchMask();
  m_LastFindReplaceData->recursiveSearch = dlg->GetRecursive();
  m_LastFindReplaceData->searchProject = dlg->GetProject();
  m_LastFindReplaceData->searchTarget = dlg->GetTarget();
  m_LastFindReplaceData->hiddenSearch = dlg->GetHidden();
  m_LastFindReplaceData->initialreplacing = false;
  m_LastFindReplaceData->NewSearch = true;
  if( control ) {
    m_LastFindReplaceData->SearchInSelectionStart = control->GetSelectionStart();
    m_LastFindReplaceData->SearchInSelectionEnd = control->GetSelectionEnd();
  }
  dlg->Destroy();
  if( m_LastFindReplaceData->regEx ) {
    if( m_LastFindReplaceData->findText.IsSameAs( '^' ) || m_LastFindReplaceData->findText.IsSameAs( '$' ) ) {
      msging( _T( "Bad regex entered!\nPlease correct regex and try again!" ), _T( "Error!" ), wxICON_ERROR );
      return 0;
    }
  }
  int ReturnValue = 0;
  if( !replace ) {
    if( m_LastFindReplaceData->findInFiles ) { }
    else
    { ReturnValue = Find( control, m_LastFindReplaceData ); }
  } else {
    m_LastFindReplaceData->initialreplacing = true;
    if( m_LastFindReplaceData->findInFiles ) {
      ReturnValue = ReplaceInFiles( m_LastFindReplaceData );
    } else
    { ReturnValue = Replace( control, m_LastFindReplaceData ); }
  }
  m_LastFindReplaceData->NewSearch = false;
  if( m_LastFindReplaceData->findInFiles ) {
    m_LastFindReplaceData->findInFiles = false;
  }
  return ReturnValue;
}

void FindReplace::CalculateFindReplaceStartEnd( M_Code_StyledTextCtrl* control, cbFindReplaceData* data, bool replace ) {
  if( !control || !data ) {
    return;
  }
  if( data->startFile ) {
    int clen = control->GetLength();
    int slen = data->findText.Len();
    data->start = 0;
    data->end = std::min( slen, clen );
  } else if( !data->findInFiles ) {
    int ssta = control->GetSelectionStart();
    int send = control->GetSelectionEnd();
    int cpos = control->GetCurrentPos();
    int clen = control->GetLength();
    if( ( data->scope == 0 ) && data->NewSearch && ( ssta != cpos || send != cpos ) ) {
      if( !replace ) {
        ssta = cpos;
        send = cpos;
      }
    }
    data->start = 0;
    data->end   = clen;
    if( !data->originEntireScope || !data->NewSearch ) {
      if( !data->directionDown ) {
        data->start = ( data->initialreplacing ) ? std::max( send, cpos ) : std::min( ssta, cpos );
      } else
      { data->start = ( data->initialreplacing ) ? std::min( ssta, cpos ) : std::max( send, cpos ); }
    } else {
      if( !data->directionDown ) {
        data->start = clen;
      }
    }
    if( !data->directionDown ) {
      data->end = 0;
    }
    if( data->scope == 1 &&
        control->GetSelectionStart() == control->GetSelectionEnd() ) {
      data->scope = 0;
    }
    if( data->scope == 1 ) {
      if( data->NewSearch ) {
        if( !data->directionDown ) {
          data->start = std::max( ssta, send );
          data->end   = std::min( ssta, send );
        } else {
          data->start = std::min( ssta, send );
          data->end   = std::max( ssta, send );
        }
      } else {
        ssta = data->SearchInSelectionStart;
        send = data->SearchInSelectionEnd;
        if( cpos < ssta || cpos > send ) {
          data->start = ssta;
          data->end = send;
        } else {
          data->start = cpos;
          data->end = ( data->directionDown ) ? send : ssta;
        }
      }
    }
  } else {
    data->start = ( replace ? 0 : control->GetCurrentPos() );
    data->end   = control->GetLength();
  }
}

int FindReplace::Replace( M_Code_StyledTextCtrl* control, cbFindReplaceData* data ) {
  if( !control || !data ) {
    return -1;
  }
  if( control->GetReadOnly() ) {
    msging( _T( "This file is read-only.\nReplacing in a read-only file is not possible." ),
            _T( "警告" ), wxICON_EXCLAMATION );
    return -1;
  }
  bool advRegex = false;
  bool advRegexNewLinePolicy = ! data->IsMultiLine();
  int replacecount = 0;
  int foundcount   = 0;
  int flags        = 0;
  {
    int eolMode = control->GetEOLMode();
    data->ConvertEOLs( eolMode );
    if( data->IsMultiLine() && data->fixEOLs ) {
      control->BeginUndoAction();
      control->SetChangeCollection( false );
      control->ConvertEOLs( eolMode );
      control->SetChangeCollection( true );
      control->EndUndoAction();
    }
  }
  control->BeginUndoAction();
  CalculateFindReplaceStartEnd( control, data, true );
  if( data->matchWord ) {
    flags |= wxSCI_FIND_WHOLEWORD;
  }
  if( data->startWord ) {
    flags |= wxSCI_FIND_WORDSTART;
  }
  if( data->matchCase ) {
    flags |= wxSCI_FIND_MATCHCASE;
  }
  if( data->regEx ) {
    flags |= wxSCI_FIND_REGEXP;
    if( Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_posix_style_regexes" ), false ) ) {
      flags |= wxSCI_FIND_POSIX;
    }
    advRegex = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_advanced_regexes" ), false );
  }
  wxRegEx re;
  if( advRegex ) {
    if( data->matchCase ) {
      re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) );
    } else
    { re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) | wxRE_ICASE ); }
  }
  int pos = -1;
  bool replace = false;
  bool confirm = true;
  bool stop = false;
  wxPoint LastDlgPosition;
  bool HaveLastDlgPosition = false;
  bool wrapAround = false;
  int  data_start_initial = data->start;
  bool wrapAroundNotification = false;
  while( !stop ) {
    int lengthFound = 0;
    if( !advRegex ) {
      pos = control->FindText( data->start, data->end, data->findText, flags, &lengthFound );
    } else {
      wxString text = control->GetTextRange( data->start, data->end );
      if( re.Matches( text ) ) {
        size_t start, len;
        re.GetMatch( &start, &len, 0 );
        pos = start + data->start;
        lengthFound = len;
        if( ( start == 0 ) && ( len == 0 ) ) {
          text = text.Mid( 1 );
          if( re.Matches( text ) ) {
            re.GetMatch( &start, &len, 0 );
            pos = start + data->start + 1;
            lengthFound = len;
          } else
          { pos = -1; }
        }
      } else
      { pos = -1; }
    }
    if( data->startFile && pos > 0 ) {
      pos = -1;
    }
    if( pos != -1 && data->start != data->end ) {
      control->GotoPos( pos );
      control->EnsureVisible( control->LineFromPosition( pos ) );
      control->SetSelectionVoid( pos, pos + lengthFound );
      data->start = pos;
      data->initialreplacing = false;
    } else if( !wrapAround ) {
      if( ( data->scope == 0 ) &&
          ( ( data->directionDown && data_start_initial != 0 ) ||
            ( !data->directionDown && data_start_initial != control->GetLength() ) ) ) {
        wxString msg;
        if( data->directionDown ) {
          msg = _T( "Text not found.\nSearch from the start of the document?" );
        } else
        { msg = _T( "Text not found.\nSearch from the end of the document?" ); }
        bool auto_wrap_around = data->autoWrapSearch;
        if( auto_wrap_around ) {
          wrapAroundNotification = true;
        }
        if( auto_wrap_around || msging( msg, _T( "Result" ), wxOK | wxCANCEL | wxICON_QUESTION ) == wxID_OK ) {
          data->end = data_start_initial;
          data->start = ( data->directionDown ) ? 0 : control->GetLength();
          wrapAround = true;
          continue;
        } else
        { break; }
      } else
      { break; }
    } else
    { break; }
    if( wrapAroundNotification ) {
      wxBell();
      InfoWindow::Tip( _T( "已到达文档结尾" ), _T( "提示" ), 1000 );
      wrapAroundNotification = false;
    }
    foundcount++;
    if( confirm ) {
      ConfirmReplaceDlg dlg( Manager::Get()->GetAppWindow() );
      if( HaveLastDlgPosition ) {
        dlg.Move( LastDlgPosition );
      } else
      { dlg.CalcPosition( control ); }
      int ans = dlg.ShowModal();
      LastDlgPosition = dlg.GetPosition();
      HaveLastDlgPosition = true;
      switch( ans ) {
        case crYes:
          replace = true;
          break;
        case crNo:
          replace = false;
          break;
        case crAll:
          replace = true;
          confirm = false;
          control->Freeze();
          break;
        case crCancel:
          stop = true;
          break;
        default:
          break;
      }
    }
    if( !stop ) {
      if( replace ) {
        int lengthReplace = data->replaceText.Length();
        replacecount++;
        if( data->regEx ) {
          control->SetTargetStart( control->GetSelectionStart() );
          control->SetTargetEnd( control->GetSelectionEnd() );
          if( advRegex ) {
            wxString text = control->GetSelectedText();
            re.Replace( &text, data->replaceText, 1 );
            lengthReplace = text.Len();
            control->ReplaceSelection( text );
          } else {
            lengthReplace = control->ReplaceTargetRE( data->replaceText );
          }
          control->SetTargetStart( 0 );
          control->SetTargetEnd( 0 );
        } else {
          control->ReplaceSelection( data->replaceText );
        }
        if( data->directionDown ) {
          data->start += lengthReplace;
        }
        int diff = lengthReplace - lengthFound;
        if( data->directionDown ) {
          data->end += diff;
        } else {
          if( data->end < diff ) {
            data->end = 0;
          } else {
            data->end -= diff;
          }
        }
      } else {
        if( data->directionDown ) {
          data->start += lengthFound;
        } else {
          data->start -= lengthFound;
        }
      }
    }
  }
  if( control->IsFrozen() ) {
    control->Thaw();
  }
  control->EndUndoAction();
  wxString msg;
  if( foundcount == 0 ) {
    msg = _T( "找不到匹配项 \"" ) + data->findText + _T( "\"" );
  } else if( replacecount == 0 && foundcount == 1 ) {
    msg = _T( "找到一个匹配项，但未替换" );
  } else {
    msg.Printf( _T( "替换 %i  匹配 %i " ), replacecount, foundcount );
  }
  InfoWindow::Tip( msg, _T( "提示" ), 2500 );
  control->SetSCIFocus( true );
  return pos;
}

int FindReplace::ReplaceInFiles( cbFindReplaceData* data ) {
  if( !data ) {
    return 0;
  }
  if( data->findText.IsEmpty() ) {
    return 0;
  }
  EditorManager *editorMgr = Manager::Get()->GetEditorManager();
  bool IsMultiLine = data->IsMultiLine();
  wxArrayString filesList;
  if( data->scope == 0 ) {
    for( int i = 0; i < editorMgr->GetEditorsCount(); ++i ) {
      M_Code_Editor* ed = editorMgr->GetBuiltinEditor( i );
      if( ed ) {
        filesList.Add( ed->GetFilename() );
      }
    }
  } else if( data->scope == 1 ) {
    if( data->searchProject < 0 ) {
      msging( _T( "没有要搜索的项目!" ), _T( "错误" ), wxICON_WARNING );
      return 0;
    }
    M_Code_Project* prj = ( *Manager::Get()->GetProjectManager()->GetProjects() )[data->searchProject];
    wxString target;
    wxString fullpath = _T( "" );
    if( data->searchTarget >= 0 ) {
      target = prj->GetBuildTarget( data->searchTarget )->GetTitle();
    }
    for( FilesList::iterator it = prj->GetFilesList().begin(); it != prj->GetFilesList().end(); ++it ) {
      ProjectFile* pf = *it;
      if( pf ) {
        if( target != wxEmptyString && pf->buildTargets.Index( target ) < 0 ) {
          continue;
        }
        fullpath = pf->file.GetFullPath();
        if( filesList.Index( fullpath ) >= 0 ) {
          continue;
        }
        if( wxFileExists( fullpath ) ) {
          filesList.Add( fullpath );
        }
      }
    }
  } else if( data->scope == 2 ) {
    const ProjectsArray* pProjects = Manager::Get()->GetProjectManager()->GetProjects();
    if( pProjects ) {
      int count = pProjects->GetCount();
      for( int idxProject = 0; idxProject < count; ++idxProject ) {
        M_Code_Project* pProject = pProjects->Item( idxProject );
        if( pProject ) {
          wxString fullpath = _T( "" );
          for( FilesList::iterator it = pProject->GetFilesList().begin(); it != pProject->GetFilesList().end(); ++it ) {
            ProjectFile* pf = *it;
            if( pf ) {
              fullpath = pf->file.GetFullPath();
              if( filesList.Index( fullpath ) == -1 ) {
                if( wxFileExists( fullpath ) ) {
                  filesList.Add( fullpath );
                }
              }
            }
          }
        }
      }
    }
  } else if( data->scope == 3 ) {
    int flags = wxDIR_FILES | ( data->recursiveSearch ? wxDIR_DIRS : 0 ) | ( data->hiddenSearch ? wxDIR_HIDDEN : 0 );
    wxArrayString masks = GetArrayFromString( data->searchMask );
    if( !masks.GetCount() ) {
      masks.Add( _T( "*" ) );
    }
    unsigned int count = masks.GetCount();
    wxLogNull ln;
    for( unsigned int i = 0; i < count; ++i ) {
      wxDir::GetAllFiles( data->searchPath, &filesList, masks[i], flags );
    }
  }
  int filesCount = filesList.GetCount();
  if( filesCount == 0 ) {
    msging( _T( "没有要搜索的文件！" ), _T( "错误" ), wxICON_WARNING );
    return 0;
  }
  bool advRegex = false;
  bool advRegexNewLinePolicy = ! data->IsMultiLine();
  int flags = 0;
  if( data->matchWord ) {
    flags |= wxSCI_FIND_WHOLEWORD;
  }
  if( data->startWord ) {
    flags |= wxSCI_FIND_WORDSTART;
  }
  if( data->matchCase ) {
    flags |= wxSCI_FIND_MATCHCASE;
  }
  if( data->regEx ) {
    flags |= wxSCI_FIND_REGEXP;
    if( Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_posix_style_regexes" ), false ) ) {
      flags |= wxSCI_FIND_POSIX;
    }
    advRegex = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_advanced_regexes" ), false );
  }
  wxRegEx re;
  if( advRegex ) {
    if( data->matchCase ) {
      re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) );
    } else {
      re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) | wxRE_ICASE );
    }
  }
  bool replace = false;
  bool confirm = true;
  bool stop = false;
  bool wholeFile = false;
  bool all = false;
  int pos = -1;
  wxPoint LastDlgPosition;
  bool HaveLastDlgPosition = false;
  wxProgressDialog* progress = nullptr;
  wxString fileContents;
  wxFontEncoding def_encoding = wxFONTENCODING_UTF8;
  cbFindReplaceData dataCopy = *data;
  int read_only_files_skipped = 0;
  for( int i = 0; i < filesCount && !stop; ++i ) {
    M_Code_Editor* ed = NULL;
    M_Code_StyledTextCtrl* control = NULL;
    bool fileWasNotOpen = false;
    if( progress ) {
      if( !progress->Update( i ) ) {
        if( msging( _T( "是否确实要停止在文件中替换?" ), _T( "审查" ), wxICON_QUESTION | wxYES_NO ) == wxID_YES ) {
          break;
        } else {
          progress->Resume();
        }
      }
    }
    EditorBase *eb = editorMgr->IsOpen( filesList[i] );
    if( eb ) {
      fileWasNotOpen = false;
      ed = editorMgr->GetBuiltinEditor( eb );
      if( ed ) {
        control = ed->GetControl();
      }
    }
    if( !control ) {
      wxFile file( filesList[i] );
      if( !file.IsOpened() ) {
        continue;
      }
      fileContents = cbReadFileContents( file, def_encoding );
      if( advRegex ) {
        if( !re.Matches( fileContents ) ) {
          continue;
        }
      } else {
        int pos_str;
        if( !data->matchCase ) {
          pos_str = fileContents.Upper().Find( data->findText.Upper() );
        } else {
          pos_str = fileContents.Find( data->findText );
        }
        if( pos_str == -1 ) {
          continue;
        }
      }
      ed = editorMgr->Open( filesList[i] );
      fileWasNotOpen = true;
      if( ed ) {
        control = ed->GetControl();
      }
    }
    if( !control || !ed ) {
      continue;
    }
    if( control->GetReadOnly() ) {
      read_only_files_skipped++;
      continue;
    }
    editorMgr->SetActiveEditor( ed );
    *data = dataCopy;
    bool replacementsWereMade = false;
    int eolMode = control->GetEOLMode();
    data->ConvertEOLs( eolMode );
    if( IsMultiLine && data->fixEOLs ) {
      control->BeginUndoAction();
      control->SetChangeCollection( false );
      control->ConvertEOLs( eolMode );
      control->SetChangeCollection( true );
      control->EndUndoAction();
    }
    control->BeginUndoAction();
    CalculateFindReplaceStartEnd( control, data, true );
    wholeFile = false;
    if( !all ) {
      confirm = true;
    }
    while( !stop || wholeFile ) {
      int lengthFound = 0;
      if( !advRegex ) {
        pos = control->FindText( data->start, data->end, data->findText, flags, &lengthFound );
      } else {
        wxString text = control->GetTextRange( data->start, data->end );
        if( re.Matches( text ) ) {
          size_t start, len;
          re.GetMatch( &start, &len, 0 );
          pos = start + data->start;
          lengthFound = len;
          if( ( start == 0 ) && ( len == 0 ) ) {
            text = text.Mid( 1 );
            if( re.Matches( text ) ) {
              re.GetMatch( &start, &len, 0 );
              pos = start + data->start + 1;
              lengthFound = len;
            } else {
              pos = -1;
            }
          }
        } else {
          pos = -1;
        }
      }
      if( data->startFile && ( pos > 0 ) ) {
        pos = -1;
      }
      if( ( pos == -1 ) || ( data->start == data->end ) ) {
        break;
      }
      if( confirm ) {
        control->GotoPos( pos );
        control->EnsureVisible( control->LineFromPosition( pos ) );
      }
      control->SetSelectionVoid( pos, pos + lengthFound );
      data->start = pos;
      data->initialreplacing = false;
      if( confirm ) {
        ConfirmReplaceDlg dlg( Manager::Get()->GetAppWindow(), true );
        if( HaveLastDlgPosition ) {
          dlg.Move( LastDlgPosition );
        } else {
          dlg.CalcPosition( control );
        }
        int ans = dlg.ShowModal();
        LastDlgPosition = dlg.GetPosition();
        HaveLastDlgPosition = true;
        switch( ans ) {
          case crYes:
            replace = true;
            break;
          case crNo:
            replace = false;
            break;
          case crAllInFile:
            confirm = false;
            replace = true;
            wholeFile = true;
            break;
          case crSkipFile:
            confirm = false;
            replace = false;
            wholeFile = true;
            break;
          case crAll:
            replace = true;
            confirm = false;
            all = true;
            progress = new wxProgressDialog( _T( "在文件中替换" ), _T( "正在替换文件，请稍候..." ), filesCount,
                                             Manager::Get()->GetAppWindow(), wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT );
            PlaceWindow( progress );
            Manager::Get()->GetAppWindow()->Freeze();
            break;
          case crCancel:
            stop = true;
            break;
          default:
            break;
        }
      }
      if( !stop ) {
        if( replace ) {
          replacementsWereMade = true;
          int lengthReplace = data->replaceText.Length();
          if( data->regEx ) {
            control->SetTargetStart( control->GetSelectionStart() );
            control->SetTargetEnd( control->GetSelectionEnd() );
            if( advRegex ) {
              wxString text = control->GetSelectedText();
              re.Replace( &text, data->replaceText, 1 );
              lengthReplace = text.Len();
              control->ReplaceSelection( text );
            } else {
              lengthReplace = control->ReplaceTargetRE( data->replaceText );
            }
            control->SetTargetStart( 0 );
            control->SetTargetEnd( 0 );
          } else {
            control->ReplaceSelection( data->replaceText );
          }
          data->start += lengthReplace;
          int diff = lengthReplace - lengthFound;
          if( data->directionDown ) {
            data->end += diff;
          } else {
            data->end -= diff;
          }
        } else {
          if( data->directionDown ) {
            data->start += lengthFound;
          } else {
            data->start -= lengthFound;
          }
        }
      }
    }
    control->EndUndoAction();
    if( !replacementsWereMade && fileWasNotOpen ) {
      editorMgr->Close( ed, true );
    }
  }
  if( read_only_files_skipped ) {
    wxString msg;
    msg.Printf( _T( "Skipped %d read-only file(s)." ), read_only_files_skipped );
    InfoWindow::Tip( msg, _T( "警告" ) );
  }
  if( progress ) {
    Manager::Get()->GetAppWindow()->Thaw();
  }
  delete progress;
  InfoWindow::Tip( _T( "文件中的替换已完成所有操作" ), _T( "提示" ), 2500 );
  return pos;
}

int FindReplace::Find( M_Code_StyledTextCtrl* control, cbFindReplaceData* data ) {
  if( !control || !data ) {
    return -1;
  }
  bool advRegex = false;
  bool advRegexNewLinePolicy = ! data->IsMultiLine();
  int flags = 0;
  data->ConvertEOLs( control->GetEOLMode() );
  CalculateFindReplaceStartEnd( control, data );
  if( data->matchWord ) {
    flags |= wxSCI_FIND_WHOLEWORD;
  }
  if( data->startWord ) {
    flags |= wxSCI_FIND_WORDSTART;
  }
  if( data->matchCase ) {
    flags |= wxSCI_FIND_MATCHCASE;
  }
  if( data->regEx ) {
    flags |= wxSCI_FIND_REGEXP;
    if( Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_posix_style_regexes" ), false ) ) {
      flags |= wxSCI_FIND_POSIX;
    }
    advRegex = Manager::Get()->GetConfigManager( _T( "editor" ) )->ReadBool( _T( "/use_advanced_regexes" ), false );
  }
  wxRegEx re;
  if( advRegex ) {
    if( data->matchCase ) {
      re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) );
    } else
    { re.Compile( data->findText, wxRE_ADVANCED | ( wxRE_NEWLINE * advRegexNewLinePolicy ) | wxRE_ICASE ); }
  }
  int pos = -1;
  bool wrapAround = false;
  int StartPos = 0;
  int EndPos = control->GetLength();
  if( data->scope == 1 ) {
    StartPos = data->SearchInSelectionStart;
    EndPos = data->SearchInSelectionEnd;
  }
  bool wrapAroundNotification = false;
  while( true ) {
    int lengthFound = 0;
    if( !advRegex ) {
      pos = control->FindText( data->start, data->end, data->findText, flags, &lengthFound );
    } else {
      wxString text = control->GetTextRange( data->start, data->end );
      if( re.Matches( text ) ) {
        size_t start, len;
        re.GetMatch( &start, &len, 0 );
        pos = start + data->start;
        lengthFound = len;
        if( ( start == 0 ) && ( len == 0 ) ) {
          text = text.Mid( 1 );
          if( re.Matches( text ) ) {
            re.GetMatch( &start, &len, 0 );
            pos = start + data->start + 1;
            lengthFound = len;
          } else {
            pos = -1;
          }
        }
      } else {
        pos = -1;
      }
    }
    if( pos != -1 && data->start != data->end ) {
      int line = control->LineFromPosition( pos );
      int onScreen = control->LinesOnScreen() >> 1;
      int l1 = line - onScreen;
      int l2 = line + onScreen;
      for( int l = l1; l <= l2; l += 2 ) {
        control->EnsureVisible( l );
      }
      control->GotoLine( l1 );
      control->GotoLine( l2 );
      control->GotoLine( line );
      control->SetSelectionVoid( pos, pos + lengthFound );
      data->start = pos;
      break;
    } else if( !wrapAround && !data->findInFiles ) {
      if( ( data->directionDown && data->start != StartPos )
          || ( !data->directionDown && data->start != EndPos ) ) {
        wxString msg;
        if( data->scope == 1 ) {
          if( data->directionDown ) {
            msg = _T( "未找到文本.\n从所选内容的开头搜索?" );
          } else {
            msg = _T( "未找到文本.\n从所选内容的结尾搜索?" );
          }
        } else {
          if( data->directionDown ) {
            msg = _T( "未找到文本.\n从文档开始处搜索?" );
          } else {
            msg = _T( "未找到文本.\n从文档结尾搜索?" );
          }
        }
        bool auto_wrap_around = data->autoWrapSearch;
        if( auto_wrap_around ) {
          wrapAroundNotification = true;
        }
        if( auto_wrap_around || msging( msg, _T( "Result" ), wxOK | wxCANCEL | wxICON_QUESTION ) == wxID_OK ) {
          wrapAround = true;
          if( data->scope == 1 ) {
            if( data->directionDown ) {
              data->start = data->SearchInSelectionStart;
              data->end = data->SearchInSelectionEnd;
            } else {
              data->start = data->SearchInSelectionEnd;
              data->end = data->SearchInSelectionStart;
            }
          } else {
            if( data->directionDown ) {
              data->start = 0;
              data->end = control->GetLength();
            } else {
              data->start = control->GetLength();
              data->end = 0;
            }
          }
        } else {
          break;
        }
      } else {
        wxString msg;
        msg.Printf( _T( "没有找到: %s" ), data->findText.c_str() );
        InfoWindow::Tip( msg, _T( "提示" ), 2500 );
        control->SetSCIFocus( true );
        wrapAroundNotification = false;
        break;
      }
    } else if( wrapAround ) {
      wxString msg;
      msg.Printf( _T( "没有找到: %s" ), data->findText.c_str() );
      InfoWindow::Tip( msg, _T( "提示" ), 2500 );
      wrapAroundNotification = false;
      break;
    } else {
      break;
    }
  }
  if( wrapAroundNotification ) {
    wxBell();
    InfoWindow::Tip( _T( "已到达文档结尾" ), _T( "提示" ), 1000 );
  }
  return pos;
}

int FindReplace::FindNext( bool goingDown, M_Code_StyledTextCtrl* control, cbFindReplaceData* data, bool selected ) {
  if( !control ) {
    M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
    if( ed ) {
      control = ed->GetControl();
    }
  }
  if( !control ) {
    return -1;
  }
  if( !data ) {
    data = m_LastFindReplaceData;
    if( data ) {
      data->findInFiles = false;
    }
  }
  if( !data ) {
    if( selected ) {
      m_LastFindReplaceData = new cbFindReplaceData;
      data = m_LastFindReplaceData;
      data->findInFiles = false;
    } else {
      return ShowFindDialog( false, false );
    }
  }
  if( !data->findInFiles ) {
    if( selected ) {
      data->start = 0;
      data->end = 0;
      data->findText = control->GetSelectedText();
      data->replaceText = wxEmptyString;
      data->eolMode = wxSCI_EOL_LF;
      data->multiLine = false;
      data->fixEOLs = false;
      data->startFile = false;
      data->findInFiles = false;
      data->matchWord = false;
      data->startWord = false;
      data->matchCase = false;
      data->regEx = false;
      data->originEntireScope = false;
      data->scope = 0;
      data->searchPath = wxEmptyString;
      data->searchMask = wxEmptyString;
      data->recursiveSearch = false;
      data->searchProject = false;
      data->searchTarget = false;
      data->hiddenSearch = false;
      data->initialreplacing = false;
      data->NewSearch = false;
    }
    wxString phraseAtCursor = control->GetSelectedText();
    if( !data->findUsesSelectedText && !selected ) {
      if( !phraseAtCursor.IsEmpty() && data->findText.IsEmpty() ) {
        data->findText = phraseAtCursor;
      }
    } else {
      if( !phraseAtCursor.IsEmpty() ) {
        data->findText = phraseAtCursor;
        data->originEntireScope = false;
        data->scope = 0;
      }
    }
  }
  data->directionDown = goingDown;
  return Find( control, data );
}

int FindReplace::FindSelectedText( bool goingDown ) {
  M_Code_Editor* ed = Manager::Get()->GetEditorManager()->GetBuiltinActiveEditor();
  if( ed ) {
    M_Code_StyledTextCtrl *stc = ed->GetControl();
    wxString text = stc->GetSelectedText();
    if( !text.size() ) {
      int iCurrentPos = stc->GetCurrentPos();
      stc->SetSelectionStart( stc->WordStartPosition( iCurrentPos, true ) );
      stc->SetSelectionEnd( stc->WordEndPosition( iCurrentPos, true ) );
    }
    return FindNext( goingDown, nullptr, nullptr, true );
  }
  return -1;
}
